#include <sobjc-commons/defines.h>
#include "command.h"
#include "context.h"
#include "parse.h"
#include "salloc.h"
#include "scope.h"
#include "type.h"

#define OBJC_TYPE_ID_NAME           "id"
#define OBJC_TYPE_CLASS_NAME        "Class"
#define OBJC_TYPE_SEL_NAME          "SEL"

static void encode_su_member_ctype(char **, objc_node_t, const char *);
static char *get_member_name(objc_node_t);

char *
objc_ctype_from_node(objc_node_t node, objc_class_t cur_cls)
{
  char *base, *buf, *ctype;
  
  base = buf = SALLOC_N(char, 512);
  *buf = 0;
  objc_encode_ctype(&buf, node, NO, cur_cls);
  *buf = 0;
  ctype = ALLOC_N(char, (ptrdiff_t)(buf - base) + 1);
  strcpy(ctype, base);
  objc_sfree(base);
  return ctype;
}

char *
objc_ctype_from_decl(objc_node_t spcr, objc_node_t dclr)
{
  char *base, *buf, *ctype;
  
  base = buf = SALLOC_N(char, 512);
  *buf = 0;
  objc_encode_ctype(&buf, dclr, YES, NULL);
  objc_encode_ctype(&buf, spcr, NO, NULL);
  *buf = 0;

  ctype = ALLOC_N(char, (ptrdiff_t)(buf - base) + 1);
  strcpy(ctype, base);
  objc_sfree(base);
  return ctype;
}
 
void
objc_encode_ctype(char **buf, objc_node_t node, BOOL is_dclr,
                  objc_class_t cur_cls)
{
  if (node == NULL)
    return;

  switch (node->type) {
  case OBJC_DECL_SPCR_LIST_NODE:
  case OBJC_EXPR_NODE:
  case OBJC_SPCR_QUAL_LIST_NODE:
  case OBJC_STRUCT_DCLR_LIST_NODE:
  case OBJC_STRUCT_DECL_LIST_NODE:
    {
      int i;
      for (i = 0; i < node->a.l.nsubs; i++)
        objc_encode_ctype(buf, node->a.l.subs[i], is_dclr, cur_cls);
    }
    break;

  case OBJC_STORAGE_CLS_SPCR_NODE:
    /* ignore */
    break;

  case OBJC_ARRAY_DIR_DCLR_NODE:
    objc_encode_ctype(buf, node->a.array_dir_dclr.dir_dclr, is_dclr, cur_cls);
    break;

  case OBJC_ASSIGN_EXPR_NODE:
    objc_encode_ctype(buf, node->a.bin_expr.left, is_dclr, cur_cls);
    break;

  case OBJC_CAST_EXPR_NODE:
    objc_encode_ctype(buf, node->a.cast_expr.type, is_dclr, cur_cls);
    break;

  case OBJC_CLS_NAME_NODE:
    if (*((*buf)-1) == OBJC_CTYPE_PTR_CODE)
      *(*buf)--;
    *buf += sprintf(*buf, "%c={%s}", OBJC_CTYPE_ID_CODE, node->a.sval);
    break;

  case OBJC_CLS_OBJ_RECV_NODE:
    *buf += sprintf(*buf, "%c={%s}", OBJC_CTYPE_CLASS_CODE, node->a.sval);
    break;

  case OBJC_CONST_NODE:
    switch (node->a.const_val.tag) {
    case INT_CONST:
      if (STREQ(node->a.const_val.val, "0"))
        *(*buf)++ = OBJC_CTYPE_ID_CODE;
      else
        *(*buf)++ = OBJC_CTYPE_INT_CODE;
      break;
    case ENUM_CONST:
      *(*buf)++ = OBJC_CTYPE_INT_CODE;
      break;
    case CHAR_CONST:
      *(*buf)++ = OBJC_CTYPE_CHAR_CODE;
      break;
    case FLOAT_CONST:
      *(*buf)++ = OBJC_CTYPE_FLOAT_CODE;
      break;
    }
    break;

  case OBJC_EQ_EXPR_NODE:
    *(*buf)++ = OBJC_CTYPE_INT_CODE;
    break;

  case OBJC_FUNC_CALL_EXPR_NODE:
    objc_encode_ctype(buf, node->a.func_call_expr.func, NO, cur_cls);
    break;

  case OBJC_FUNC_DEF_NODE:
    {
      char *ret_type;

      ret_type = objc_ctype_from_decl(node->a.func_def.decl_spcr_list,
                                      node->a.func_def.dclr);
      *buf += sprintf(*buf, "%c%s%c", OBJC_CTYPE_FUNC_BEGIN_CODE,
                      ret_type, OBJC_CTYPE_FUNC_END_CODE);
      objc_sfree(ret_type);
    }
    break;

  case OBJC_FUNC_DIR_DCLR_NODE:
    objc_encode_ctype(buf, node->a.func_dir_dclr.dir_dclr, YES, cur_cls);
    break;

  case OBJC_INC_OR_DEC_EXPR_NODE:
    objc_encode_ctype(buf, node->a.inc_or_dec_expr.expr, is_dclr, cur_cls);
    break;

  case OBJC_IVAR_DECL_NODE:
    objc_encode_ctype(buf, node->a.ivar_decl.dclr, YES, cur_cls);
    objc_encode_ctype(buf, node->a.ivar_decl.spcr_qual_list, NO, cur_cls);
    break;

  case OBJC_TYPE_NAME_NODE:
    objc_encode_ctype(buf, node->a.type_name.abs_dclr, YES, cur_cls);
    objc_encode_ctype(buf, node->a.type_name.spcr_qual_list, NO, cur_cls);
    break;

  case OBJC_TYPEDEF_NAME_NODE:
    {
      objc_scope_entry_t entry;

      if (STREQ(node->a.sval, OBJC_TYPE_ID_NAME))
        *(*buf)++ = OBJC_CTYPE_ID_CODE;
      else if (STREQ(node->a.sval, OBJC_TYPE_SEL_NAME))
        *(*buf)++ = OBJC_CTYPE_SEL_CODE;
      else if (STREQ(node->a.sval, OBJC_TYPE_CLASS_NAME))
        *(*buf)++ = OBJC_CTYPE_CLASS_CODE;
      else {
        entry = objc_scope_get_type(objc_current_scope, node->a.sval);
        if (entry->a.type.ctype == NULL)
          entry->a.type.ctype = objc_ctype_from_decl(entry->a.type.spcr, entry->a.type.dclr);
        *buf += sprintf(*buf, "%s", entry->a.type.ctype);
      }
    }
    break;

  case OBJC_INIT_DCLR_NODE:
    objc_encode_ctype(buf, node->a.init_dclr.dclr, YES, cur_cls);
    break;

  case OBJC_DCLR_NODE:
    objc_encode_ctype(buf, node->a.dclr.base_dclr, YES, cur_cls);
    break;

  case OBJC_BASE_DCLR_NODE:
    objc_encode_ctype(buf, node->a.base_dclr.ptr, NO, cur_cls);
    objc_encode_ctype(buf, node->a.base_dclr.dir_dclr, YES, cur_cls);
    break;

  case OBJC_TYPE_QUAL_NODE:
    switch (node->a.word) {
    case CONST: case CONST2: case CONST3:
      *(*buf)++ = OBJC_CTYPE_CONST_CODE;
      break;
    }
    break;

  case OBJC_TYPE_SPCR_NODE:
    switch (node->a.word) {
    case UNSIGNED:  *(*buf)++ = OBJC_CTYPE_UNSIGNED_CODE; break;
    case CHAR:      *(*buf)++ = OBJC_CTYPE_CHAR_CODE; break;
    case SHORT:     *(*buf)++ = OBJC_CTYPE_SHORT_CODE; break;
    case INT:       *(*buf)++ = OBJC_CTYPE_INT_CODE; break;
    case LONG:      *(*buf)++ = OBJC_CTYPE_LONG_CODE; break;
    case FLOAT:     *(*buf)++ = OBJC_CTYPE_FLOAT_CODE; break;
    case DOUBLE:    *(*buf)++ = OBJC_CTYPE_DOUBLE_CODE; break;
    case VOID:      *(*buf)++ = OBJC_CTYPE_VOID_CODE; break;
    }
    break;

  case OBJC_KW_DCLR_NODE:
    objc_encode_ctype(buf, node->a.kw_dclr.mtype, YES, cur_cls);
    break;

  case OBJC_KW_MSG_EXPR_NODE:
    {
      objc_class_t cls;
      BOOL is_inst;
      objc_method_t meth;
      objc_method_sig_t sig;

      cls = objc_class_type_from_node(node->a.kw_msg_expr.recv,
                                      &is_inst, cur_cls);
      meth = objc_class_get_method(cls, node->a.kw_msg_expr.sel_name, is_inst);
      if (meth)
        sig = meth->sig;
      else
        sig = objc_get_method_sig(node->a.kw_msg_expr.sel_name);
      objc_encode_ctype(buf, sig->mtype, is_dclr, cur_cls);
    }
    break;

  case OBJC_ABS_DCLR_NODE:
    objc_encode_ctype(buf, node->a.abs_dclr.ptr, NO, cur_cls);
    objc_encode_ctype(buf, node->a.abs_dclr.dir_abs_dclr, YES, cur_cls);
    break;

  case OBJC_PTR_NODE:
    *(*buf)++ = OBJC_CTYPE_PTR_CODE;
    objc_encode_ctype(buf, node->a.ptr.ptr, NO, cur_cls);
    break;

  case OBJC_IDENT_NODE:
    {
      objc_ivar_t ivar;
      objc_scope_entry_t entry;

      if (is_dclr)
        break;

      if (STREQ(node->a.sval, "self") && cur_cls)
        *(*buf)++ = OBJC_CTYPE_SELF_CODE;
      else if (STREQ(node->a.sval, "super") && cur_cls)
        *(*buf)++ = OBJC_CTYPE_SUPER_CODE;
      else if (cur_cls && (ivar =
                           objc_class_get_ivar(cur_cls, node->a.sval)) != NULL) {
        if (ivar->ctype == NULL)
          ivar->ctype =
            objc_ctype_from_decl(ivar->node->a.ivar_decl.spcr_qual_list,
                                 ivar->node->a.ivar_decl.dclr);
        *buf += sprintf(*buf, "%s", ivar->ctype);
      } else {
        entry = objc_scope_get_var(objc_current_scope, node->a.sval);
        if (!entry)
          objc_print_error("not found entry for '%s'", node->a.sval);
        if (entry->a.type.ctype == NULL)
          entry->a.type.ctype = objc_ctype_from_decl(entry->a.type.spcr,
                                                     entry->a.type.dclr);
        *buf += sprintf(*buf, "%s", entry->a.type.ctype);
      }
    }
    break;

  case OBJC_SU_REF_EXPR_NODE:
    {
      char *ctype, *su_name = NULL, *split;
      size_t su_name_len;
      objc_scope_entry_t entry;

      ctype = objc_ctype_from_node(node->a.su_ref_expr.expr, cur_cls);
      if (*ctype == OBJC_CTYPE_CLASS_CODE) {
        entry = objc_scope_get_struct(objc_current_scope, "objc_class");
        encode_su_member_ctype(buf, entry->a.type.spcr,
                               node->a.su_ref_expr.name);
      } else {
        if ((*ctype == OBJC_CTYPE_PTR_CODE && 
             (ctype[1] == OBJC_CTYPE_STRUCT_BEGIN_CODE ||
              ctype[1] == OBJC_CTYPE_UNION_BEGIN_CODE)) &&
            (split = strchr(ctype, '=')) != NULL) {
          su_name_len = split - ctype - 2;
          su_name = SALLOC_N(char, su_name_len+1);
          strncpy(su_name, ctype+2, su_name_len);
        } else if ((*ctype == OBJC_CTYPE_STRUCT_BEGIN_CODE ||
                    *ctype == OBJC_CTYPE_UNION_BEGIN_CODE) &&
                   (split = strchr(ctype, '=')) != NULL) {
          su_name_len = split - ctype - 2;
          su_name = SALLOC_N(char, su_name_len+1);
          strncpy(su_name, ctype+1, su_name_len+1);
        }

        if (su_name) {
          entry = objc_scope_get_struct(objc_current_scope, su_name);
          encode_su_member_ctype(buf, entry->a.type.spcr,
                                 node->a.su_ref_expr.name);
          objc_sfree(su_name);
        } else {
          /* IMPLEMENT ME? anonymous structure or union */
          *(*buf)++ = OBJC_CTYPE_UNKNOWN_CODE;
        }
        objc_sfree(ctype);
      }
    }
    break;

  case OBJC_SU_SPCR_NODE:
    {
      objc_scope_entry_t entry;

      if (node->a.su_spcr.type)
        *(*buf)++ = OBJC_CTYPE_STRUCT_BEGIN_CODE;
      else
        *(*buf)++ = OBJC_CTYPE_UNION_BEGIN_CODE;

      if (node->a.su_spcr.name != NULL)
        *buf += sprintf(*buf, "%s=", node->a.su_spcr.name);

      if (node->a.su_spcr.name != NULL) {
        entry = objc_scope_get_struct(objc_root_scope, node->a.su_spcr.name);
        if (entry)
          objc_encode_ctype(buf, entry->a.type.spcr->a.su_spcr.decl_list,
                            NO, cur_cls);
      } else if (node->a.su_spcr.decl_list)
        objc_encode_ctype(buf, node->a.su_spcr.decl_list, NO, cur_cls);

      if (node->a.su_spcr.type)
        *(*buf)++ = OBJC_CTYPE_STRUCT_END_CODE;
      else
        *(*buf)++ = OBJC_CTYPE_UNION_END_CODE;
    }
    break;

  case OBJC_STRUCT_DECL_NODE:
    objc_encode_ctype(buf, node->a.struct_decl.dclr_list, YES, cur_cls);
    objc_encode_ctype(buf, node->a.struct_decl.spcr_qual_list, NO, cur_cls);
    break;

  case OBJC_STRUCT_DCLR_NODE:
    /* bitfield */
    /*
    if (node->a.struct_dclr.expr)
      type->body.nbits = node->a.struct_dclr.expr;
    else
      objc_encode_ctype(buf, node->a.struct_dclr.dclr, YES, cur_cls);
    */
    if (!node->a.struct_dclr.expr)
      objc_encode_ctype(buf, node->a.struct_dclr.dclr, YES, cur_cls);
    break;

  case OBJC_UNARY_MSG_EXPR_NODE:
    {
      objc_class_t cls;
      objc_method_t meth;
      objc_method_sig_t sig;
      BOOL is_inst;

      cls = objc_class_type_from_node(node->a.unary_msg_expr.recv,
                                      &is_inst, cur_cls);
      meth = objc_class_get_method(cls, node->a.unary_msg_expr.sel_name, is_inst);
      if (meth)
        sig = meth->sig;
      else
        sig = objc_get_method_sig(node->a.unary_msg_expr.sel_name);
      objc_encode_ctype(buf, sig->mtype, NO, cur_cls);
    }
    break;

  default:
    objc_print_error("cannot convert node %s to ctype encoding",
                     objc_node_name(node));
  }
}

static void
encode_su_member_ctype(char **buf, objc_node_t spcr, const char *name)
{
  objc_node_t decls, decl, dclrs, dclr, base_dclr;
  int i, j;
  char *mem, *ctype;

  decls = spcr->a.su_spcr.decl_list;
  for (i = 0; i < decls->a.l.nsubs; i++) {
    decl = decls->a.l.subs[i];
    dclrs = decl->a.struct_decl.dclr_list;
    for (j = 0; j < dclrs->a.l.nsubs; j++) {
      dclr = dclrs->a.l.subs[j];
      if (dclr->a.struct_dclr.dclr) {
        base_dclr = dclr->a.struct_dclr.dclr->a.dclr.base_dclr;
        mem = get_member_name(base_dclr->a.base_dclr.dir_dclr);
        if (STREQ(name, mem)) {
          ctype = objc_ctype_from_decl(decl->a.struct_decl.spcr_qual_list,
                                       base_dclr);
          *buf += sprintf(*buf, "%s", ctype);
          return;
        }
      }
    }
  }
}

static char *
get_member_name(objc_node_t node)
{
  switch (node->type) {
  case OBJC_IDENT_NODE:
    return node->a.sval;

  case OBJC_DCLR_NODE:
    return get_member_name(node->a.base_dclr.dir_dclr);

  case OBJC_ARRAY_DIR_DCLR_NODE:
    return get_member_name(node->a.array_dir_dclr.dir_dclr);

  case OBJC_FUNC_DIR_DCLR_NODE:
    return get_member_name(node->a.func_dir_dclr.dir_dclr);

  default:
    objc_print_error("get_member_name: not supported %s\n",
                     objc_node_name(node));
  }

  return NULL;
}

objc_class_t
objc_class_type_from_node(objc_node_t node, BOOL *is_inst,
                          objc_class_t cur_cls)
{
  char *ctype, *name;
  size_t name_len;
  objc_class_t cls = NULL;

  ctype = objc_ctype_from_node(node, cur_cls);
  if (ctype[0] == OBJC_CTYPE_ID_CODE) {
    *is_inst = YES;
    if (ctype[1]) {
      name = SALLOC_N(char, strlen(ctype));
      name_len = strlen(ctype) - 4;
      strncpy(name, ctype+3, name_len);
      name[name_len] = 0;
      cls = objc_get_class(name);
      objc_sfree(name);
    } else
      cls = objc_root_class;

  } else if (ctype[0] == OBJC_CTYPE_CLASS_CODE) {
    *is_inst = NO;
    if (ctype[1]) {
      name = SALLOC_N(char, strlen(ctype));
      name_len = strlen(ctype) - 5;
      strncpy(name, ctype+4, name_len);
      name[name_len] = 0;
      cls = objc_get_class(name);
      objc_sfree(name);
    } else
      cls = objc_root_class;
  } else if (ctype[0] == OBJC_CTYPE_SELF_CODE)
    cls = cur_cls;

  objc_free(ctype);
  return cls;
}

